#include <fs/fs.h>
#include <fs/types.h>
#include <fs/err.h>

LIST_HEAD(super_blocks);

/**
 *	alloc_super	-	create new superblock
 *	@type:	filesystem type superblock should belong to
 *
 *	Allocates and initializes a new &struct super_block.  alloc_super()
 *	returns a pointer new superblock or %NULL if allocation had failed.
 */
static struct super_block *alloc_super(struct file_system_type *type)
{
	struct super_block *s = xmalloc_bytes(sizeof(struct super_block));
	//static struct super_operations default_op;

	if (s) {
		//if (security_sb_alloc(s)) {
		//	xfree(s);
		//	s = NULL;
		//	goto out;
		//}
		INIT_LIST_HEAD(&s->s_dirty);
		INIT_LIST_HEAD(&s->s_io);
		INIT_LIST_HEAD(&s->s_more_io);
		INIT_LIST_HEAD(&s->s_files);
		INIT_LIST_HEAD(&s->s_instances);
		INIT_HLIST_HEAD(&s->s_anon);
		INIT_LIST_HEAD(&s->s_inodes);
		INIT_LIST_HEAD(&s->s_dentry_lru);
		INIT_LIST_HEAD(&s->s_async_list);
		//init_rwsem(&s->s_umount);
		//mutex_init(&s->s_lock);
		//lockdep_set_class(&s->s_umount, &type->s_umount_key);
		/*
		 * The locking rules for s_lock are up to the
		 * filesystem. For example ext3fs has different
		 * lock ordering than usbfs:
		 */
		//lockdep_set_class(&s->s_lock, &type->s_lock_key);
		/*
		 * sget() can have s_umount recursion.
		 *
		 * When it cannot find a suitable sb, it allocates a new
		 * one (this one), and tries again to find a suitable old
		 * one.
		 *
		 * In case that succeeds, it will acquire the s_umount
		 * lock of the old one. Since these are clearly distrinct
		 * locks, and this object isn't exposed yet, there's no
		 * risk of deadlocks.
		 *
		 * Annotate this by putting this lock in a different
		 * subclass.
		 */
		//down_write_nested(&s->s_umount, SINGLE_DEPTH_NESTING);
		s->s_count = S_BIAS;
		atomic_set(&s->s_active, 1);
		//mutex_init(&s->s_vfs_rename_mutex);
		//mutex_init(&s->s_dquot.dqio_mutex);
		//mutex_init(&s->s_dquot.dqonoff_mutex);
		//init_rwsem(&s->s_dquot.dqptr_sem);
		//init_waitqueue_head(&s->s_wait_unfrozen);
		s->s_maxbytes = MAX_NON_LFS;
		//s->dq_op = sb_dquot_ops;
		//s->s_qcop = sb_quotactl_ops;
		//s->s_op = &default_op;
		s->s_time_gran = 1000000000;
	}
out:
	return s;
}

/**
 *	get_super - get the superblock of a device
 *	@bdev: device to get the superblock for
 *	
 *	Scans the superblock list and finds the superblock of the file system
 *	mounted on the device given. %NULL is returned if no match is found.
 */

struct super_block * get_super(struct block_device *bdev)
{
	struct super_block *sb;
	sb = list_entry(super_blocks.next, struct super_block, s_list);
	return sb;
	//if (!bdev)
	//	return NULL;

	//spin_lock(&sb_lock);
//rescan:
	//list_for_each_entry(sb, &super_blocks, s_list) {
		//if (sb->s_bdev == bdev) {
		//if((char *)sb->s_type->name == 'm'){
		//	sb->s_count++;
			//spin_unlock(&sb_lock);
			//down_read(&sb->s_umount);
			//if (sb->s_root)
			//	return sb;
			//up_read(&sb->s_umount);
			/* restart only when sb is no longer on the list */
			//spin_lock(&sb_lock);
			//if (__put_super_and_need_restart(sb))
			//	goto rescan;
		//}
	//}
	//spin_unlock(&sb_lock);
	//return NULL;
}

EXPORT_SYMBOL(get_super);

/**
 *	sget	-	find or create a superblock
 *	@type:	filesystem type superblock should belong to
 *	@test:	comparison callback
 *	@set:	setup callback
 *	@data:	argument to each of them
 */
struct super_block *sget(struct file_system_type *type,
			int (*test)(struct super_block *,void *),
			int (*set)(struct super_block *,void *),
			void *data)
{
	struct super_block *s = NULL;
	struct super_block *old;
	int err;
retry:
	//spin_lock(&sb_lock);
	/*
	if(test){
		list_for_each_entry(old, &type->fs_supers, s_instances){
			if(!test(old, data))
				continue;
			//if(!grab_super(old))
			//	goto retry;
			if(s){
				//up_write(&s->s_umount);
				//destroy_super(s);
			}
			return old;
		}
	}*/
	if(!s){
		//spin_unlock(&sb_lock);
		s = alloc_super(type);
		if(!s)
			return ERR_PTR(-ENOMEM);
		goto retry;
	}

	//err = set(s, data);
	//if (err) {
	//	spin_unlock(&sb_lock);
	//	up_write(&s->s_umount);
	//	destroy_super(s);
	//	return ERR_PTR(err);
	//}
	s->s_type = type;
	strlcpy(s->s_id, type->name, sizeof(s->s_id));
	list_add_tail(&s->s_list, &super_blocks);
	list_add(&s->s_instances, &type->fs_supers);
	//spin_unlock(&sb_lock);
	//get_filesystem(type);
	return s;	
}

int set_anon_super(struct super_block *s, void *data)
{
	int dev;
	int error;

 retry:
	/*
	if (ida_pre_get(&unnamed_dev_ida, GFP_ATOMIC) == 0)
		return -ENOMEM;
	spin_lock(&unnamed_dev_lock);
	error = ida_get_new(&unnamed_dev_ida, &dev);
	spin_unlock(&unnamed_dev_lock);
	if (error == -EAGAIN)
		// We raced and lost with another CPU. 
		goto retry;
	else if (error)
		return -EAGAIN;

	if ((dev & MAX_ID_MASK) == (1 << MINORBITS)) {
		spin_lock(&unnamed_dev_lock);
		ida_remove(&unnamed_dev_ida, dev);
		spin_unlock(&unnamed_dev_lock);
		return -EMFILE;
	}
	s->s_dev = MKDEV(0, dev & MINORMASK);
	*/
	return 0;
}

static int set_bdev_super(struct super_block *s, void *data)
{
	s->s_bdev = data;
	s->s_dev = s->s_bdev->bd_dev;
	return 0;
}

static int test_bdev_super(struct super_block *s, void *data)
{
	//return (void *)s->s_bdev == data;
	return s != NULL;
}

int get_sb_bdev(struct file_system_type *fs_type,
	int flags, const char *dev_name, void *data,
	int (*fill_super)(struct super_block *, void *, int),
	struct vfsmount *mnt)
{
	struct block_device *bdev;
	struct super_block *s;
	fmode_t mode = FMODE_READ;
	int error = 0;
	
	//if (!(flags & MS_RDONLY))
		//mode |= FMODE_WRITE;

	//bdev = open_bdev_exclusive(dev_name, mode, fs_type);
	if (IS_ERR(bdev))
		return PTR_ERR(bdev);

	/*
	 * once the super is inserted into the list by sget, s_umount
	 * will protect the lockfs code from trying to start a snapshot
	 * while we are mounting
	 */
	//down(&bdev->bd_mount_sem);
	s = sget(fs_type, test_bdev_super, set_bdev_super, bdev);
	//up(&bdev->bd_mount_sem);
	if (IS_ERR(s))
		goto error_s;

	if (s->s_root) {
		if ((flags ^ s->s_flags) & MS_RDONLY) {
			//up_write(&s->s_umount);
			//deactivate_super(s);
			error = -EBUSY;
			goto error_bdev;
		}

		//close_bdev_exclusive(bdev, mode);
	} else {
		char b[BDEVNAME_SIZE];

		s->s_flags = flags;
		//s->s_mode = mode;
		//strlcpy(s->s_id, bdevname(bdev, b), sizeof(s->s_id));
		//sb_set_blocksize(s, block_size(bdev));
		error = fill_super(s, data, flags & MS_SILENT ? 1 : 0);
		if (error) {
			//up_write(&s->s_umount);
			//deactivate_super(s);
			goto error;
		}

		s->s_flags |= MS_ACTIVE;
		//bdev->bd_super = s;
	}

	return 0;//simple_set_mnt(mnt, s);

error_s:
	error = PTR_ERR(s);
error_bdev:
	//close_bdev_exclusive(bdev, mode);
error:
	return error;
}

EXPORT_SYMBOL(get_sb_bdev);

int get_sb_nodev(struct file_system_type *fs_type,
	int flags, void *data,
	int (*fill_super)(struct super_block *, void *, int),
	struct vfsmount *mnt)
{
	int error;
	struct super_block *s = sget(fs_type, NULL, set_anon_super, NULL);
	if (IS_ERR(s))
		return -1;//PTR_ERR(s);

	s->s_flags = flags;

	error = fill_super(s, data, flags & MS_SILENT ? 1 : 0);
	if (error) {
		//up_write(&s->s_umount);
		//deactivate_super(s);
		return error;
	}
	s->s_flags |= MS_ACTIVE;
	return 0;//simple_set_mnt(mnt, s);
}

EXPORT_SYMBOL(get_sb_nodev);
